﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace iNet.Net.Html
{
	/// <summary>
	/// 提供循序讀取 HTML 文件的功能。
	/// </summary>
	public class HtmlReader
	{
		#region 欄位
		Regex _CommentRegex = new Regex(@"\<!\-{2}(([^\-]|\-+[^\-\>]+)+\-{2,}\>|\-*\>)", RegexOptions.Singleline);
		int _CurrentPosition;
		Regex _DocTypeRegex = new Regex("\\<\\!DOCTYPE([^'\"\\>\\<]|'[^']*'|\"[^\"]*\")*\\>", RegexOptions.Singleline);
		string _Html;
		HtmlTag _PreviousTag;
		Regex _ScriptRegex = new Regex("(?<Script>((\\<\\s*/\\s*script\\s*\\>){0}|[^'\"]|'[^']*'|\"[^\"]*\")*)(\\<\\s*/\\s*script\\s*\\>){1}", RegexOptions.IgnoreCase | RegexOptions.Singleline);
		bool _SkipNewLine = true;
		Regex _StyleSheetRegex = new Regex("(?<StyleSheet>((\\<\\s*/\\s*style\\s*\\>){0}|[^'\"]|'[^']*'|\"[^\"]*\")*)(\\<\\s*/\\s*style\\s*\\>){1}", RegexOptions.IgnoreCase | RegexOptions.Singleline);
		Regex _TagRegex = new Regex("\\<([^'\"\\>\\<]|'[^']*'|\"[^\"]*\")*\\>", RegexOptions.Singleline);
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 HtmlReader 執行個體。
		/// </summary>
		/// <param name="html">HTML 原始碼。</param>
		/// <exception cref="ArgumentNullException">html 參數值為 Null (Visual Basic 為 Nothing)。</exception>
		public HtmlReader(string html)
		{
			if (html == null)
				throw new ArgumentNullException("html");
			_Html = html;
		}
		#endregion
		#region Html
		/// <summary>
		/// 取得 HTML 原始碼。
		/// </summary>
		public string Html
		{
			get
			{
				return _Html;
			}
		}
		#endregion
		#region Read
		/// <summary>
		/// 讀取下一個 HTML 元素。
		/// </summary>
		/// <returns>讀取到的 HTML 元素，Null (Visual Basic 為 Nothing) 表示已經讀取到文件結尾。</returns>
		public HtmlElement Read()
		{
			//讀取元素
			while (_CurrentPosition < _Html.Length)
			{
				//讀取字元
				char c = _Html[_CurrentPosition];

				//讀取元素
				HtmlElement element;
				if (c == '<')
				{
					int index = _CurrentPosition;
					if (index < _Html.Length - 1)
					{
						index++;
						if (_Html[index] == '!')
						{
							if (index < _Html.Length - 1)
							{
								if (_Html[index + 1] == 'D')
									element = this.ReadTag(HtmlElementType.DocType);
								else
									element = this.ReadTag(HtmlElementType.Comment);
							}
							else
								element = this.ReadText();
						}
						else
							element = this.ReadTag(HtmlElementType.Tag);
					}
					else
						element = this.ReadText();
				}
				else
				{
					if (_PreviousTag != null && _PreviousTag.IsStartTag && !_PreviousTag.IsEndTag)
					{
						switch (_PreviousTag.Name)
						{
							case "script":
								element = this.ReadScript();
								break;
							case "style":
								element = this.ReadStyleSheet();
								break;
							default:
								element = this.ReadText();
								break;
						}
					}
					else
						element = this.ReadText();
				}

				//確認讀取結果
				if (element == null)
					continue;

				//設定欄位
				if (element.Type == HtmlElementType.Tag)
					_PreviousTag = element as HtmlTag;

				//回傳
				return element;
			}

			//回傳
			return null;
		}
		#endregion
		#region ReadScript
		HtmlElement ReadScript()
		{
			Match match = _ScriptRegex.Match(_Html, _CurrentPosition);
			if (match.Success && match.Index == _CurrentPosition)
			{
				string script = match.Groups["Script"].Value;
				_CurrentPosition += script.Length;
				return new HtmlElement(HtmlElementType.Script, script);
			}
			else
				return this.ReadText();
		}
		#endregion
		#region ReadStyleSheet
		HtmlElement ReadStyleSheet()
		{
			Match match = _StyleSheetRegex.Match(_Html, _CurrentPosition);
			if (match.Success && match.Index == _CurrentPosition)
			{
				string styleSheet = match.Groups["StyleSheet"].Value;
				_CurrentPosition += styleSheet.Length;
				return new HtmlElement(HtmlElementType.Stylesheet, styleSheet);
			}
			else
				return this.ReadText();
		}
		#endregion
		#region ReadTag
		HtmlElement ReadTag(HtmlElementType type)
		{
			//比對字串
			Match match;
			switch (type)
			{
				case HtmlElementType.Comment:
					match = _CommentRegex.Match(_Html, _CurrentPosition);
					break;
				case HtmlElementType.DocType:
					match = _DocTypeRegex.Match(_Html, _CurrentPosition);
					break;
				case HtmlElementType.Tag:
					match = _TagRegex.Match(_Html, _CurrentPosition);
					break;
				default:
					return this.ReadText();
			}

			//取出字串
			if (match.Success && match.Index == _CurrentPosition)
			{
				_CurrentPosition += match.Length;
				switch (type)
				{
					case HtmlElementType.Comment:
						return new HtmlElement(HtmlElementType.Comment, match.Value);
					case HtmlElementType.DocType:
						return new HtmlDocType(match.Value);
					case HtmlElementType.Tag:
						return new HtmlTag(match.Value);
					default:
						return null;
				}
			}
			else
				return this.ReadText();
		}
		#endregion
		#region ReadText
		HtmlElement ReadText()
		{
			//讀取字串
			StringBuilder buffer = new StringBuilder();
			int index = _CurrentPosition;
			int htmlLength = _Html.Length;
			char c = _Html[index++];
			if ((c != '\n' && c != '\r') || !_SkipNewLine)
				buffer.Append(c);
			while (index < htmlLength)
			{
				c = _Html[index++];
				if (c == '<')
				{
					index--;
					break;
				}
				else if ((c == '\n' || c == '\r') && _SkipNewLine)
					continue;
				buffer.Append(c);
			}
			_CurrentPosition = index;

			//回傳
			if (buffer.Length == 0)
				return null;
			return new HtmlElement(HtmlElementType.Text, buffer.ToString());
		}
		#endregion
		#region Reset
		/// <summary>
		/// 將讀取位置移到 HTML 最前端。
		/// </summary>
		public void Reset()
		{
			_CurrentPosition = 0;
			_PreviousTag = null;
		}
		#endregion
		#region SkipNewLine
		/// <summary>
		/// 取得或設定讀取 HTML 字串時是否要忽略換行符號，預設值為 True。
		/// </summary>
		public bool SkipNewLine
		{
			get
			{
				return _SkipNewLine;
			}
			set
			{
				_SkipNewLine = value;
			}
		}
		#endregion
	}
}